package com.jl.js.core;

import java.util.Arrays;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSStaticFunction;

import com.jl.process.util.ByteBuffer;
import com.jl.util.HexUtil;

public class ByteArray extends ScriptableObject implements Wrapper {

	private final static String clazzName = "ByteArray";
	private ByteBuffer buffer = null;

	public ByteArray() {
	}

	public ByteArray(byte[] bytes) {
		this.buffer = new ByteBuffer(bytes.clone());
	}
	
	public ByteArray(String hex){
		this.buffer = new ByteBuffer(HexUtil.parseHexToByteArray(hex));
	}

	@JSConstructor
	public static Scriptable jsConstructor(Context ctx, Object[] args,
			Function ctorObj, boolean inNewExpr) {
		ByteArray ba = new ByteArray();
		if (args[0] instanceof byte[]) {
			ba.buffer = new ByteBuffer((byte[]) args[0]);
		} else {
			String str = Context.toString(args[0]);
			ba.buffer = new ByteBuffer(HexUtil.parseHexToByteArray(str));
		}
		return ba;
	}

	public static ByteArray newInstance(Scriptable scope, byte[] bytes) {
		Object[] params = { bytes };
		Context cx = Context.getCurrentContext();
		return (ByteArray) cx.newObject(scope, clazzName, params);
	}

	public static ByteArray newInstance(Scriptable scope, String data,
			int encoding) {
		Object[] params = { data, new Integer(encoding) };
		Context cx = Context.getCurrentContext();
		return (ByteArray) cx.newObject(scope, clazzName, params);
	}

	@Override
	public String getClassName() {
		return clazzName;
	}

	@Override
	public Object unwrap() {
		return buffer.getBytes();
	}

	public byte[] getBytes() {
		return buffer.getBytes();
	}

	/**
	 * 
	 * @param offset
	 *            0 base
	 * @param len
	 *            length
	 * @return
	 */
	public byte[] getBytes(int offset, int len) {
		return buffer.getBytes(offset, len);
	}

	@JSFunction
	@Override
	public String toString() {
		return HexUtil.toHexString(buffer.getBytes());
	}

	@Override
	@JSFunction
	public boolean equals(Object obj) {
		if (super.equals(obj)) {
			return true;
		} else {
			if (!(obj instanceof ByteArray)) {
				if (obj instanceof String) {// String,if is hex equsls return
											// true
					ByteArray t = new ByteArray(
							HexUtil.parseHexToByteArray((String) obj));
					return equals(t);
				}
				return false;
			} else {
				ByteArray temp = (ByteArray) obj;
				byte[] t = temp.getBytes();
				byte[] t2 = this.getBytes();
				if (t.length != t2.length) {
					return false;
				}
				for (int i = 0; i < t.length; i++) {
					if (t[i] != t2[i]) {
						return false;
					}
				}
				return true;
			}
		}
	}

	/**
	 * length
	 * 
	 * @return
	 */
	@JSGetter
	public int length() {
		return buffer.length();
	}

	/**
	 * format
	 * 
	 * @param step
	 * @return
	 */
	@JSFunction
	public String format(int step) {
		if (step <= 0) {
			step = 16;
		}
		StringBuilder sb = new StringBuilder();
		byte[] buffer = this.buffer.getBytes();
		if (buffer.length < step) {
			step = buffer.length;
		}
		int i = 0;
		while (i < buffer.length) {
			sb.append(HexUtil.toHexString(buffer, i, step)).append('\n');
			i += step;
			if (step > buffer.length - i) {
				step = buffer.length - i;
			}
		}
		return sb.toString().trim();
	}

	/**
	 * dump
	 * 
	 * @return
	 */
	@JSFunction
	public String dump() {
		return HexUtil.dump(getBytes(), 0, length());
	}

	/**
	 * sub array
	 * 
	 * @param from
	 *            0 base.contain from
	 * @param to
	 *            0 base,not contain to
	 * @return
	 */
	@JSFunction
	public byte[] sub(int from, int to) {
		return buffer.getBytes(from, to - from);
	}

	/**
	 * getByte
	 * 
	 * @param index
	 * @return
	 */
	@JSFunction
	public byte getByte(int index) {
		return buffer.getByteAt(index);
	}

	/**
	 * setByte
	 * 
	 * @param index
	 * @param value
	 */
	@JSFunction
	public void setByte(int index, int value) {
		buffer.setByteAt(index, (byte) (value & 0xFF));
	}

	/**
	 * appendByte
	 * 
	 * @param value
	 * @return
	 */
	@JSFunction
	public ByteArray appendByte(int value) {
		buffer.append((byte) (value & 0xFF));
		return this;
	}

	/**
	 * append
	 * 
	 * @param ba
	 * @return
	 */
	@JSFunction
	public ByteArray append(ByteArray ba) {
		buffer.append(ba.getBytes());
		return this;
	}

	/**
	 * appendHexString
	 * 
	 * @param hex
	 * @return
	 */
	@JSFunction
	public ByteArray appendHexString(String hex) {
		buffer.append(HexUtil.parseHexToByteArray(hex));
		return this;
	}

	/**
	 * insertByte
	 * 
	 * @param offset
	 * @param value
	 * @return
	 */
	@JSFunction
	public ByteArray insertByte(int offset, int value) {
		buffer.insert(offset, (byte) (value & 0xFF));
		return this;
	}

	/**
	 * insert
	 * 
	 * @param offset
	 * @param ba
	 * @return
	 */
	@JSFunction
	public ByteArray insert(int offset, ByteArray ba) {
		buffer.insert(offset, ba.getBytes());
		return this;
	}

	/**
	 * insertHexString
	 * 
	 * @param offset
	 * @param hex
	 * @return this
	 */
	@JSFunction
	public ByteArray insertHexString(int offset, String hex) {
		buffer.insert(offset, HexUtil.parseHexToByteArray(hex));
		return this;
	}

	/**
	 * Search ByteBuffer for matching search string and return zero based
	 * offset. Start search at given offset. An empty search string is always
	 * found.
	 * 
	 * @param ba
	 * @param baOffset
	 * @return
	 */
	@JSFunction
	public int find(ByteArray ba, int baOffset) {
		return buffer.find(ba.getBytes(), baOffset);
	}

	/**
	 * getInt
	 * 
	 * @param off
	 * @return
	 */
	@JSFunction
	public int getInt(int off) {
		if (off < 0 || off > length() - 4) {
			throw new IndexOutOfBoundsException();
		}
		return (HexUtil.makeInt(getBytes(), off, 4) & 0xFFFFFFFF);
	}

	/**
	 * getShort
	 * 
	 * @param off
	 * @return
	 */
	@JSFunction
	public int getShort(int off) {
		if (off < 0 || off > length() - 2) {
			throw new IndexOutOfBoundsException();
		}
		return HexUtil.makeShort(getBytes(), off);
	}

	/**
	 * setInt
	 * 
	 * @param off
	 * @param val
	 */
	@JSFunction
	public void setInt(int off, int val) {
		if (off < 0 || off + 4 > length()) {
			throw new IndexOutOfBoundsException();
		}
		String temp = String.format("%08X", val & 0xFFFFFFFFL);
		byte[] b = HexUtil.parseHexToByteArray(temp);
		for (int i = 0; i < 4; i++) {
			this.buffer.setByteAt(off + i, b[i]);
		}
	}

	/**
	 * setShort
	 * 
	 * @param off
	 * @param val
	 */
	@JSFunction
	public void setShort(int off, int val) {
		if (off < 0 || off + 2 > length()) {
			throw new IndexOutOfBoundsException();
		}
		String temp = String.format("%04X", val & 0xFFFF);
		byte[] b = HexUtil.parseHexToByteArray(temp);
		for (int i = 0; i < 2; i++) {
			this.buffer.setByteAt(off + i, b[i]);
		}
	}

	/**
	 * insertInt
	 * 
	 * @param off
	 * @param val
	 */
	@JSFunction
	public void insertInt(int off, int val) {
		String temp = String.format("%08X", val & 0xFFFFFFFFL);
		this.insertHexString(off, temp);
	}

	/**
	 * insertShort
	 * 
	 * @param off
	 * @param val
	 */
	@JSFunction
	public void insertShort(int off, int val) {
		String temp = String.format("%04X", val & 0xFFFF);
		this.insertHexString(off, temp);
	}

	/**
	 * appendInt
	 * 
	 * @param val
	 */
	@JSFunction
	public void appendInt(int val) {
		String temp = String.format("%08X", val & 0xFFFFFFFFL);
		this.appendHexString(temp);
	}

	/**
	 * appendShort
	 * 
	 * @param val
	 */
	@JSFunction
	public void appendShort(int val) {
		String temp = String.format("%04X", val & 0xFFFF);
		this.appendHexString(temp);
	}

	@JSStaticFunction
	public static byte[] fromDecimal(String dec) {
		String[] arr = dec.trim().split("\\s+");
		byte[] res = new byte[arr.length];
		int idx = 0;
		for (String str : arr) {
			res[idx++] = (byte) (Integer.parseInt(str) & 0xFF);
		}
		return res;
	}

	@JSStaticFunction
	public static byte[] fromHex0x(String hex0x) {
		String[] arr = hex0x.trim().toLowerCase().split("0x");
		byte[] res = new byte[arr.length];
		int idx = 0;
		for (String str : arr) {
			str = str.trim();
			if(str.equals("")){
				continue;
			}
			res[idx++] = (byte) (Integer.parseInt(str, 16) & 0xFF);
		}
		return Arrays.copyOfRange(res, 0, idx);
	}

}
